#include <stdint.h>
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;

#include "Hashes.h"

class timer {
    clock_t fTimer;
public:
    timer()
        : fTimer(clock())
    {
    }
    double elapsed() const
    {
        return (double)(clock() - fTimer)/(double)(CLOCKS_PER_SEC);
    }
    void reset()
    {
        fTimer = clock();
    }
};

void show_elapsed(double end, int iter)
{
    cout << setw(10) << (end * 1000.0) << '\t' << flush;
}

template<class HASH>
uint32_t timingUint32(const HASH& h, int iter)
{
    // make sure we're not optimizing too much
    volatile uint32_t tmp;
    timer t;
    for (int i = 0; i < iter; ++i) {
        tmp = h.hash32(i);
    }
    show_elapsed(t.elapsed(), iter);
    return tmp;
}


template<class HASH>
uint32_t  timingStringSmall(const HASH& h, int iter)
{
    // make sure we're not optimizing too much
    volatile uint32_t tmp;
    char buf[32];

    timer t;
    for (int i = 0; i < iter; ++i) {
        buf[0] = (char) i; // somehow buf needs to change else optimize does weird stuff
        tmp = h.hashStr((const char*)buf, sizeof(buf) - (i%4)); // change size to throw off optimizer
    }
    show_elapsed(t.elapsed(), iter);
    return tmp;
}

template<class HASH>
void timingStringMedium(const HASH& h, int iter)
{

    // make sure we're not optimizing too much
    volatile uint32_t tmp;
    timer t;
    char buf[128];
    for (int i = 0; i < iter; ++i) {
        buf[0] = (char)i;
        tmp = h.hashStr(buf, sizeof(buf) - (i%4));
    }
    show_elapsed(t.elapsed(), iter);
}

template<class HASH>
void timingStringLarge(const HASH& h, int iter)
{

    // make sure we're not optimizing too much
    volatile uint32_t tmp;
    timer t;
    char buf[512];
    for (int i = 0; i < iter; ++i) {
        tmp = h.hashStr(buf, sizeof(buf) - i%4);// something to throw off the optimizer
    }
    show_elapsed(t.elapsed(), iter);
}


template<class HASH>
void run(int iter, const HASH& h)
{
    cout << setw(30) << h.name() << ": " << flush;
    timingUint32(h, iter);
    timingStringSmall(h, iter);
    timingStringMedium(h, iter);
    //timingStringLarge(HASH(), iter, name);
    cout << endl;
}

int main(int argc, char* argv[])
{
    if (argc != 2) {
        std::cerr << "usage: " << argv[0] << " iterations" << std::endl;
        return 1;
    }

    int iter = strtol(argv[1], NULL, 10);


    run(iter, Times33());
    run(iter, PaulHsiehHash32());
    run(iter, NGalbreathHash32_random16());
    run(iter, NGalbreathHash32_xorplus1());
    run(iter, ThomasWangHash32_Jan2007());
    run(iter, ThomasWangHash32_Jan2007_alt());
    run(iter, ThomasWangHash32_orig());
    run(iter, ThomasWangHash32_mult());
    run(iter, FNVHash32());
    run(iter, RobertJenkinsHash32());
    run(iter, BobJenkins_lookup3());

    return 0;
}
